import numpy as np


def load_data():
    with open('连续值数据.txt') as fp:
        lines = fp.readlines()

    x = np.empty((len(lines), 8), dtype=float)
    y = np.empty(len(lines), dtype=float)

    for i in range(len(lines)):
        line = lines[i].strip().split(',')
        x[i] = line[:8]
        y[i] = line[8]

    return x, y


x, y = load_data()

N, M = x.shape


def get_probability(col, value, _y):
    """
    给定y和某列值的情况下求概率,离散值
    """
    # 根据_y分割出_x
    _x = x[y == _y]
    # 分子是在_x中列col=value的数量,+1是做拉普拉斯平滑
    # 拉普拉斯平滑(Laplacian smoothing) 是为了解决零概率的问题
    # 零概率问题:在计算事件的概率时,如果某个事件在观察样本库（训练集）中没有出现过
    # 会导致该事件的概率结果是0,这是不合理的,不能因为一个事件没有观察到,
    # 就被认为该事件一定不可能发生(即该事件的概率为 0)
    # 法国数学家 拉普拉斯 最早提出用 +1 的方法,估计没有出现过的现象的概率
    # 理论假设: 假定训练样本很大时,每个分量 𝑥 的计数 +1 造成的估计概率变化可以忽略不计
    # 但可以方便有效的避免零概率问题
    fz = (_x[:, col] == value).sum() + 1
    # 分母是_x的数量,加col列的取值数量做拉普拉斯平滑
    fm = len(_x) + len(np.unique(_x[:, col]))
    return fz / fm


def get_continuous_probability(col, value, _y):
    """
    给定y和某列值的情况下求概率,连续值
    """
    # 常量
    sqrt_2_pi = (2 * np.pi) ** 0.5
    # 切分数据
    _x = x[y == _y]
    # 求均值和方差
    mu = _x[:, col].mean()
    sigma = _x[:, col].std()
    # 计算第一部分
    p = 1 / (sqrt_2_pi * sigma)
    # 计算第二部分
    fz = (value - mu) ** 2
    fm = sigma ** 2 * 2
    p *= np.exp(-fz / fm)
    return p


def predict(_x):
    # 结果是两个概率,因为是对数概率,所以初始化为0
    # 如果不是对数就需要初始化为1
    ps = np.zeros(2)
    # 遍历2个y
    for _y in range(2):
        # 遍历所有列
        for col in range(M):
            p = None
            # 6和7是连续值,其他的是离散值
            if col == 6 or col == 7:
                p = get_continuous_probability(col, _x[col], _y)
            else:
                p = get_probability(col, _x[col], _y)
            # 对数概率,连乘变连加
            ps[_y] += np.log(p)
    # 取概率最高的y输出
    return ps.argmax()


correct = 0
for xi, yi in zip(x, y):
    if predict(xi) == yi:
        correct += 1
print(correct / N)
